package com.ace.lab.starter.utils;

import com.ace.lab.starter.exception.InvalidRpcClientClassException;
import com.ace.lab.starter.exception.ParseRpcClassException;
import com.google.protobuf.Descriptors;
import io.grpc.Channel;
import io.grpc.ServiceDescriptor;
import io.grpc.protobuf.ProtoFileDescriptorSupplier;
import io.grpc.stub.AbstractStub;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class RpcClassHelper {

    private static final String ASYNC_STUB_SUFFIX = "Stub";
    private static final String BLOCK_STUB_SUFFIX = "BlockingStub";
    private static final String FUTURE_STUB_SUFFIX = "FutureStub";
    private static final String GRPC_CLASS_SUFFIX = "Grpc";

    public static String genServiceName(Class<?> innerClass) {
        ServiceDescriptor serviceDescriptor = getServiceDescriptor(innerClass);
        ProtoFileDescriptorSupplier schemaDescriptor = (ProtoFileDescriptorSupplier) serviceDescriptor.getSchemaDescriptor();
        Descriptors.FileDescriptor fileDescriptor = schemaDescriptor.getFileDescriptor();
        String javaPackage = fileDescriptor.getOptions().getJavaPackage();
        String name = serviceDescriptor.getName();
        return javaPackage + "." + name;
    }

    public static Class<?> getGrpcClass(Class<?> innerClass) {
        return innerClass.getDeclaringClass();
    }

    public static ServiceDescriptor getServiceDescriptor(Class<?> innerClass) {
        Class<?> grpcClass = getGrpcClass(innerClass);
        try {
            Method method = grpcClass.getDeclaredMethod("getServiceDescriptor");
            return (ServiceDescriptor) method.invoke(grpcClass);
        } catch (Exception e) {
            throw new ParseRpcClassException("get Service's ServiceDescriptor error.", e);
        }
    }

    public static Descriptors.FileDescriptor getServiceProtoFileDescriptor(Class<?> innerClass) {
        ServiceDescriptor serviceDescriptor = getServiceDescriptor(innerClass);
        ProtoFileDescriptorSupplier fileDescriptorSupplier = (ProtoFileDescriptorSupplier) serviceDescriptor.getSchemaDescriptor();
        if (fileDescriptorSupplier == null) {
            throw new ParseRpcClassException("get Service's ProtoFileDescriptor error null.");
        }
        return fileDescriptorSupplier.getFileDescriptor();
    }

    public static boolean isStubClass(Class<?> clazz) {
        return isFutureStubClass(clazz) || isBlockStubClass(clazz) || isAsyncStubClass(clazz);
    }

    public static boolean isFutureStubClass(Class<?> clazz) {
        return AbstractStub.class.isAssignableFrom(clazz) && clazz.getName().endsWith(FUTURE_STUB_SUFFIX);
    }

    public static boolean isBlockStubClass(Class<?> clazz) {
        return AbstractStub.class.isAssignableFrom(clazz) && clazz.getName().endsWith(BLOCK_STUB_SUFFIX);
    }

    public static boolean isAsyncStubClass(Class<?> clazz) {
        return !isBlockStubClass(clazz) && !isFutureStubClass(clazz) && clazz.getName().endsWith(ASYNC_STUB_SUFFIX);
    }

    @SuppressWarnings("unchecked")
    public static <T extends AbstractStub> T getClientInstance(Class<T> clientClass, Channel channel) throws Exception {
        Class<?> grpcClass = getGrpcClass(clientClass);
        if (isFutureStubClass(clientClass)) {
            Method method = grpcClass.getDeclaredMethod("newFutureStub", Channel.class);
            return (T) method.invoke(grpcClass, channel);
        } else if (isBlockStubClass(clientClass)) {
            Method method = grpcClass.getDeclaredMethod("newBlockingStub", Channel.class);
            return (T) method.invoke(grpcClass, channel);
        } else if (isAsyncStubClass(clientClass)) {
            Method method = grpcClass.getDeclaredMethod("newStub", Channel.class);
            return (T) method.invoke(grpcClass, channel);
        }
        throw new InvalidRpcClientClassException("unknown rpc client class.");
    }

    public static boolean isMatchService(Class<?> innerClass, Descriptors.ServiceDescriptor serviceDescriptor) {
        Class<?> grpcClass = getGrpcClass(innerClass);
        String javaPackage = serviceDescriptor.getFile().getOptions().getJavaPackage();
        return (javaPackage + "." + serviceDescriptor.getName() + GRPC_CLASS_SUFFIX).equals(grpcClass.getName());
    }

    public static List<String> getComsumeServiceNames(Class<? extends AbstractStub> clientClass) {
        List<String> names = new ArrayList<>();
        return names;
    }
}
